/*
  Copyright © 2010  Vinícius dos Santos Oliveira

      This file is part of Touché.

      Touché is free software: you can redistribute it and/or modify
      it under the terms of the GNU Lesser General Public License as published
      by the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.

      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU Lesser General Public License for more details.

      You should have received a copy of the GNU Lesser General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */

#include "stage.h"
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsScene>
#include <QPropertyAnimation>
#include "stageactor.h"
#include "stageactorlink.h"
#include "scene.h"
#include <QDebug>
using namespace Touche;

//TODO: agora os elementos não são mais todos do mesmo tamanho, logo, os atributos abaixo deveriam ser removidos
static QSizeF m_elementSize(64., 64.);
static qreal m_elementCenter[2] = {32., 32.};
static QPointF m_elementHiddenPos(400., 240.);
static QPointF m_elementInitialPos(400., 240.);
static QPointF m_elementTrashPos(400., 480.);

// class VoidItem

class VoidItem: public QGraphicsItem
{
    friend class Stage;

protected:
    virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value)
    {
        if(change == ItemPositionChange){
            QRectF self(value.toPointF(), (1. / QGraphicsItem::scale()) * boundingRect().size());
            QRectF parent = parentItem()->boundingRect();
            if(!parent.contains(self)){
                QPointF newPos = self.topLeft();
                newPos.setX(qMin(parent.right() -self.width(), qMax(newPos.x(), parent.left())));
                newPos.setY(qMin(parent.bottom() -self.height(), qMax(newPos.y(), parent.top())));
                return newPos;
            }
        }
        return QGraphicsItem::itemChange(change, value);
    }

public:
    VoidItem(QGraphicsItem *parent) :
            QGraphicsItem(parent)
    {
        QGraphicsItem::setFlag(QGraphicsItem::ItemHasNoContents);
        QGraphicsItem::setFlag(QGraphicsItem::ItemSendsGeometryChanges);
    }

    QRectF boundingRect() const
    {
        return QRectF(0., 0., 800., 480.);
    }

    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *)
    {
    }
};

// class Stage

QScopedPointer<Stage> Stage::m_instance;
static const QList<StageActorLink *> &elementLinks = StageActorLink::links();

Stage * Stage::makeInstance(QGraphicsItem *parentItem)
{
    if(m_instance){
        m_instance->deleteLater();
        m_instance.take();
    }
    m_instance.reset(new Stage(parentItem));
    return m_instance.data();
}

Stage * Stage::getInstance()
{
    return m_instance.data();
}

Stage::Stage(QGraphicsItem *parentItem) :
        QGraphicsObject(parentItem),
        m_items(new VoidItem(this))
{
    QGraphicsItem::setTransformOriginPoint(QGraphicsItem::mapFromScene(400., 240.));
}

Stage::~Stage()
{
    for(QListIterator<StageActor *> i(elements);i.hasNext();){
        i.next()->deleteLater();
    }
}

QRectF Stage::boundingRect() const
{
    return QRectF(-400., -240., 1600., 960.);
}

void Stage::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    static QPen pen(QColor(240, 206, 17), 4., Qt::DashLine);
    painter->setPen(pen);

    StageActorLink::mutex.lock();
    for(QListIterator<StageActorLink *> i(elementLinks);i.hasNext();){
        if(StageActorLink *link = i.next()) {
            QVector<StageActor *> actors = link->actors();
            QGraphicsItem *items[2] = {actors[0]->m_item,
                                       actors[1]->m_item};

            QPointF points[2] = {QGraphicsItem::mapFromItem(items[0], items[0]->boundingRect().center()),
                                 QGraphicsItem::mapFromItem(items[1], items[1]->boundingRect().center())};
            QLineF path(points[0], points[1]);
            qreal length = path.length();
            qreal itemsRadius[2] = {items[0]->boundingRect().width() / 2.,
                                     items[1]->boundingRect().width() / 2.};

            if (length > itemsRadius[0] + itemsRadius[1]) {
                QPointF edgeOffset[2] = {QPoint((path.dx() * itemsRadius[0]) / length,
                                                (path.dy() * itemsRadius[0]) / length),
                                         QPointF((path.dx() * itemsRadius[1]) / length,
                                                 (path.dy() * itemsRadius[1]) / length)};
                points[0] = path.p1() + edgeOffset[0];
                points[1] = path.p2() - edgeOffset[1];
            } else {
                points[0] = points[1] = path.p1();
            }
            path = QLineF(points[0], points[1]);
            painter->drawLine(path);
        }
    }
    StageActorLink::mutex.unlock();
}

void Stage::addToGroup(Element *element)
{
    StageActor *item = new StageActor(element);
    elements.push_back(item);
    connect(item, SIGNAL(destroyed(QObject*)), this, SLOT(onElementDeleted(QObject*)));
    QGraphicsItem *gItem = item->item();

    gItem->setScale(0.);
    gItem->setTransformOriginPoint(gItem->boundingRect().center());

    gItem->setParentItem(m_items);

    gItem->setPos(m_items->mapFromScene(m_elementHiddenPos) - gItem->boundingRect().center());
    connect(item->item(), SIGNAL(xChanged()), reinterpret_cast<Scene *>(item->item()->scene()), SLOT(itemMoved()));
    connect(item->item(), SIGNAL(yChanged()), reinterpret_cast<Scene *>(item->item()->scene()), SLOT(itemMoved()));
    reinterpret_cast<Scene *>(item->item()->scene())->itemMoved();
    gItem->show();

    QPropertyAnimation *animation = new QPropertyAnimation(gItem->toGraphicsObject(), "scale");
    animation->setDuration(500);
//    animation->setEasingCurve(QEasingCurve::InOutExpo);
    animation->setEasingCurve(QEasingCurve::OutElastic);

    animation->setEndValue(1.);
    animation->start(QAbstractAnimation::DeleteWhenStopped);
}

void Stage::setElementSize(const QSizeF &size)
{
    m_elementSize = size;
    m_elementCenter[0] = size.width() / 2.;
    m_elementCenter[1] = size.height() / 2.;
}

void Stage::setElementHiddenPos(const QPointF &pos)
{
    m_elementHiddenPos = pos;
}

void Stage::setElementInitialPos(const QPointF &pos)
{
    m_elementInitialPos = pos;
}

void Stage::setElementTrashPos(const QPointF &pos)
{
    m_elementTrashPos = pos;
}

QSizeF Touche::Stage::elementSize()
{
    return m_elementSize;
}

QPointF Touche::Stage::elementHiddenPos()
{
    return m_elementHiddenPos;
}

QPointF Touche::Stage::elementInitialPos()
{
    return m_elementInitialPos;
}

void Stage::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mousePressEvent(event);
    pressedAt = event->screenPos();
    event->accept();
}

void Stage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mouseReleaseEvent(event);
    if(StageActor::opened){
        StageActor::opened->closeMenu();
    }
}

void Stage::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mouseMoveEvent(event);
    m_items->setPos(QPointF(m_items->pos() + (1. / m_items->scale()) *
                            (event->pos() - event->lastPos())));
    QGraphicsItem::update();
}

void Stage::onElementDeleted(QObject *element)
{
    elements.removeAt(elements.indexOf(reinterpret_cast<StageActor *>(element)));
}
